﻿@*This page constructs the Find In Files tab on the far left panel
    It allows users to search within the source/archive files*@

@page "/findInFiles"
@using StructuredLogViewer
@using StructuredLogViewer.Core
@using System.Collections
@using Microsoft.Build.Logging.StructuredLogger
@using System.IO

<RadzenTextBox Value=@searchKeyword
               Placeholder="Search..."
               Change="@(args => Change(args, "SearchBox"))"
               Style="margin-bottom: 20px"
               aria-label="Find in files" />
<br />
<br />
@if (String.IsNullOrEmpty(searchKeyword))
{
    <div>Search in files </div>
}
else
{
    <RadzenTree Data="@findResults"
                Style="word-break:break-all"
                Expand="@((TreeExpandEventArgs args) => { ExpandResults(args); })"
                Change="@OnChange">
        <RadzenTreeLevel Expanded="@((obj) => (obj is TreeNode && ((TreeNode)obj).HasChildren))"
                         Template="@TreeFormatting.TreeDesign"
                         Text="@TreeFormatting.TextSelector"
                         HasChildren="@((node) =>
                                    {
                                        if (node is TreeNode)
                                            return ((TreeNode)node).HasChildren;
                                        else return false;
                                    })" />
    </RadzenTree>
}

@code {
    [CascadingParameter]
    public SplitPane ContainerSplit { get; set; }
    public static IEnumerable<BaseNode> findResults;
    public static string searchKeyword;

    /// <summary>
    /// Fired when a user selects a tree node from the results
    /// Opens the file and highlights the line where the info can be found
    /// </summary>
    /// <param name="args"> Handler that holds the event (.Value is the tree node selected) </param>
    void OnChange(TreeEventArgs args)
    {
        object[] sourceFileInfo = SourceFileHelper.SourceFileText(ContainerSplit.archiveFileResolver, (BaseNode)args.Value);
        ContainerSplit.sourceFileName = (string)sourceFileInfo[0];
        ContainerSplit.sourceFileText = (string)sourceFileInfo[1];
        ContainerSplit.sourceFileLineNumber = (int)sourceFileInfo[2];
        ContainerSplit.sourceFileExtension = (string)sourceFileInfo[3];

        if (!ContainerSplit.showSourceFilePanel)
        {
            ContainerSplit.showSourceFilePanel = true;
            ContainerSplit.Render();
        }
        ContainerSplit.SourceFileRender();
    }

    /// <summary>
    /// Fired when a user types something into the search bar
    /// Producest the search results from the keyword
    /// </summary>
    /// <param name="value"> Keyword searched for </param>
    /// <param name="name"> Name of search box that value is coming from </param>
    void Change(string value, string name)
    {
        searchKeyword = value;
        var results = SearchInFiles(value, 1000, new System.Threading.CancellationToken());
        findResults = (IEnumerable<BaseNode>)BuildFindResults(results, value);
    }

    /// <summary>
    /// Defines the expansion of the tree.
    /// Ensures that the results will be fully layed out to begin with.
    /// </summary>
    /// <param name="args"> Handler that holds the event (.Value is the tree node selected) </param>
    public static void ExpandResults(TreeExpandEventArgs args)
    {
        TreeFormatting.OnExpand(args);
        if (args.Value is TreeNode && ((TreeNode)(args.Value)).HasChildren)
        {
            args.Children.Expanded = (obj) => (obj is TreeNode && ((TreeNode)obj).HasChildren);
        }
    }

    /// <summary>
    /// Finds a list of where the keyword can be found
    /// Does not create the results tree, just produces a list of the nodes with the keyword
    /// </summary>
    /// <param name="searchText"> keyword to find </param>
    /// <param name="maxResults"> maximum number of results to find </param>
    /// <param name="cancellationToken"> Determines if thread shold stop searching </param>
    /// <returns></returns>
    private object SearchInFiles(string searchText, int maxResults, System.Threading.CancellationToken cancellationToken)
    {
        var results = new List<(string, IEnumerable<(int, string)>)>();
        ArchiveFileResolver archiveFile = ContainerSplit.archiveFileResolver;

        foreach (var file in archiveFile.Files)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return null;
            }

            var haystack = file.Value;
            var resultsInFile = haystack.Find(searchText);
            if (resultsInFile.Count > 0)
            {
                results.Add((file.Key, resultsInFile.Select(lineNumber => (lineNumber, haystack.GetLineText(lineNumber)))));
            }
        }

        return results;
    }

    /// <summary>
    /// Builds a tree from the found results
    /// The found results are simply the nodes where the keyword has been found in,
    /// this finds the parents and creates a full tree from that information
    /// </summary>
    /// <param name="resultsObject">list of tree nodes where the searchKeyword was found</param>
    /// <param name="searchText">keyword to search for, used to determine if any results have been found</param>
    /// <returns></returns>
    private IEnumerable BuildFindResults(object resultsObject, string searchText)
    {
        if (resultsObject == null)
        {
            return null;
        }

        var results = resultsObject as IEnumerable<(string, IEnumerable<(int, string)>)>;

        var root = new Folder();

        if (results != null)
        {
            foreach (var file in results)
            {
                var folder = new Microsoft.Build.Logging.StructuredLogger.SourceFile
                {
                    Name = Path.GetFileName(file.Item1),
                    SourceFilePath = file.Item1,
                };
                root.AddChild(folder);
                foreach (var line in file.Item2)
                {
                    var sourceFileLine = new SourceFileLine()
                    {
                        LineNumber = line.Item1 + 1,
                        LineText = line.Item2
                    };
                    folder.AddChild(sourceFileLine);
                }
            }
        }

        if (!root.HasChildren && !string.IsNullOrEmpty(searchText))
        {
            root.Children.Add(new Message { Text = "No results found." });
        }

        return root.Children;
    }
}
